Release 10.1A: OpenEdge Development:
ProDataSets
Passing a ProDataSet parameter by reference
You can only specify
BY-REFERENCEon an internal procedure or user-defined function call, not on aRUNof an external procedure. When you pass a ProDataSet parameter by reference, the called routine’s ProDataSet definition is bound to the calling routine’s ProDataSet only for the duration of the call.To pass a ProDataSet by reference, in the calling routine, use the
BY-REFERENCEkeyword in theRUNstatement that defines the ProDataSet parameter. There is no special syntax required in the called routine. However, since the calling routine’s ProDataSet instance is substituted for the called routine’s ProDataSet, only the definition of the ProDataSet is required by the called routine. In other words, the same ProDataSet is defined in both routines but only one is actually used. If the called routine’s ProDataSet is not directly used to hold its own data anywhere within the routine, you can save the overhead of allocating it by including theREFERENCE-ONLYkeyword on its definition. This keyword tells Progress to use the definition for compiler references to the table and its fields but not to instantiate it at run time. Any reference to the ProDataSet, except where it is passed in from another routineBY-REFERENCE, results in a runtime error.Because of the efficiency of passing the ProDataSet with
BY-REFERENCE, you should normally use this keyword in your parameter definitions inRUNstatements for any case where the called procedure will always or sometimes be in the same session as the caller. Because Progress ignores the keyword on a remote call, you get the most efficient behavior in either case.Why then is
BY-REFERENCEnot the default behavior?Passing objects by copying them is the standard for other Progress parameter types. Passing a ProDataSet by reference has certain side effects that you need to be aware of, which could make the behavior confusing if you are not conscious of what Progress is actually doing in the background to enable your called procedure to point to the same ProDataSet instance as the caller. For this reason, you have to make a specific request to pass by reference, and you should always make sure you have considered the consequences before doing so. Since this is different from how Progress behaves otherwise, some of the effects can seem counter-intuitive, so we explain them in some detail here.
In general you must consider that on a call
BY-REFERENCE, Progress substitutes the ProDataSet handle in the calling procedure for the ProDataSet defined in the called procedure, and that is the cause of most of the side effects. Consider the cases described in the following sections.INPUT BY-REFERENCE can be like INPUT-OUTPUT
In a local call, passing a ProDataSet as an
Design tip: If the called procedure can make changes that should be visible in the caller, then you should make the parameterINPUTparameterBY-REFERENCEmakes it behave essentially like anINPUT-OUTPUTparameter. The called procedure uses its local definition of the ProDataSet, if any, only to verify that the definition is compatible with theINPUTparameter. It then adjusts any references to the parameter to point to the ProDataSet instance in the caller. As a result, any changes you make to data in the ProDataSet in the called procedure are actually made to the ProDataSet in the calling procedure, and so are visible there after the procedure returns.INPUT-OUTPUT. This reflects what’s actually going on, and also provides the correct behavior when the call is remote, as the changes must explicitly be passed back to the caller in that case. If the called procedure will only read the data and not change it, then make the parameterINPUT.Why not always make the parameter
INPUT-OUTPUTin this case?If you configure your application so that the call is made to a remote session and the called procedure doesn’t make any changes to the ProDataSet, then Progress will needlessly pass the unchanged ProDataSet back to the caller, creating unnecessary network traffic. This is why the
INPUTmode is still useful.OUTPUT BY-REFERENCE can be like OUTPUT APPEND
If the ProDataSet parameter is
Design tip: When you pass anOUTPUTBY-REFERENCEand the call is local, then any data added to the ProDataSet in the called procedure is effectively appended to what was already there, again because both procedures are actually pointing at the same ProDataSet instance. If you make the same call remotely or you don’t make itBY-REFERENCE, then the target ProDataSet in the calling procedure is emptied automatically by Progress before the data from theOUTPUTparameter is copied into it. This can give you different behavior between local and remoteRUNstatements.OUTPUTparameterBY-REFERENCE, and don’t wantAPPENDbehavior, always explicitly empty the ProDataSet in the calling procedure just before the call.This might seem like extra overhead, but in fact if you execute the statement
hDataSet:EMPTY-DATASET(), this executes exactly the same code internally as Progress uses to empty the ProDataSet for you, so the net cost is the same. If you do wantAPPENDbehavior, then include theAPPENDkeyword on the parameter as you would for a temp-table.ProDataSet instance passed BY-REFERENCE must exist in the caller
Consider this situation where a ProDataSet is passed as an
OUTPUTparameter. The calling procedure defines only a handle for the ProDataSet and passes it using theDATASET-HANDLEparameter form, without creating a dynamic ProDataSet first:
The intention is that the
called-procedure’sProDataSet definition and data are passed back as theOUTPUTparameter to populate thedataset-handlein the caller. This cannot work properly when the call is local. When the call is made, thedataset-handlehas the Unknown value (?) because it has not been used yet. But theBY-REFERENCEkeyword instructs Progress to use the calling procedure’s ProDataSet as the basis for the parameter. Since there is no such ProDataSet at the time of the call, this results in an error. A ProDataSet passedBY-REFERENCE, regardless of the parameter mode, must be initialized by specifying its tables and relations before the call.In this case the calling procedure must do one of the following:
- It can create a ProDataSet using the handle and use dynamic methods to create a structure of temp-tables and relations for it that are compatible with the ProDataSet in the
called-procedure. In this way the calling procedure’s dynamic ProDataSet is used for the call, and the data from thecalled-procedureis effectively appended to it, just as for the static example described earlier.- It can omit the
BY-REFERENCEkeyword and pass the ProDataSet by value in all cases, which means that thecalled-procedure’sProDataSet definition and data are copied back into the calling procedure to instantiate a dynamic ProDataSet there.- It can assign the handle to some valid existing static or dynamic ProDataSet before the call.
If the
OUTPUTparameter is aDATASET-HANDLE, it presumably means that the calling procedure is prepared to accept a variety of ProDataSet definitions returned to it. If this is the case, then once a particular ProDataSet has been passed back and has populated the dynamic ProDataSet, any further calls using the sameRUNstatement form must receive back a ProDataSet of the same type. Even though Progress empties the target dynamic ProDataSet so that the data from thecalled-procedurereplaces any data in the calling procedure’s ProDataSet, Progress does not automatically delete the dynamic ProDataSet structure in the calling procedure. If it is not compatible with theOUTPUTparameter, an error results.If you want to use the same dynamic ProDataSet handle to receive different ProDataSets during the lifetime of the calling procedure, you must delete the dynamic ProDataSet using the
DELETE OBJECTstatement. Set the handle variable to the Unknown value (?) before you run acalled-procedureto get back a different ProDataSet to avoid the error.Sometimes the calling procedure needs to get back a dynamic ProDataSet (that is, one with a variable definition) on the first call and then wants to be able to make further calls to get back new or additional data in the same ProDataSet type, but without copying the ProDataSet definition locally on subsequent calls. If this is the case, then the first
RUNstatement must be made by value, in order to get back the ProDataSet definition with the data, and then subsequent calls can be made with a separateRUNstatementBY-REFERENCE. This avoids copying the ProDataSet definition locally. (Note that in these descriptions the words “by value” are normally not capitalized in order to emphasize that this is the default behavior, so that the keywordBY-VALUEis not required to get this behavior.)Naturally you could not pass an uninitialized dynamic ProDataSet as an
Design tip: To summarize: if the target ProDataSet in the calling procedure is dynamic, then you must either initialize it before making a call to another local procedureINPUTorINPUT-OUTPUTparameter, as there would be no definition to pass to thecalled-procedure. In such a case you could only pass the handle variable and let thecalled-procedureassociate it with a ProDataSet of its own.BY-REFERENCE, or make the first call by value and subsequent callsBY-REFERENCEso that you obtain the ProDataSet definition the caller needs.Main block references ignored in internal procedures
When the called procedure is a persistent procedure, its ProDataSet definition will naturally be in the main block of the procedure, that is, outside of any internal procedure. A ProDataSet definition is in fact not even allowed in an internal procedure. However, if its internal procedures are ever passed a ProDataSet parameter
BY-REFERENCE, it is important that you not reference the ProDataSet handle in any way in the main block if you expect the effect of that reference to be visible in the internal procedures. This case is insidious enough to merit a specific example and diagram.Here’s a simple procedure that defines the
dsOrderProDataSet, runs another procedure persistent, and then runs an internal procedure to fill the ProDataSet:
Here’s the procedure it runs. It defines its own instance of the ProDataSet and then uses its handle to attach three Data-Sources. Inside the internal procedure
fillProc, it fills the ProDataSet and returns it as anOUTPUTparameter, as shown:
If you run the main procedure
refCaller.p, you get the following error:
![]()
What happened? All three Data-Sources were attached in the main block, so why can’t Progress see them?
The reason is that the instance of
dsOrderdefined in the main block, the one whose handle was used to attach the Data-Sources, isn’t the one used by the internal procedure. Because the ProDataSet is passed in by reference,fillProcis pointing to the instance ofdsOrderdefined inrefCaller.p, which has no Data-Sources. A few messages confirm this.Let’s display the ProDataSet handle in the calling procedure:
Also, in the main block of the persistent procedure
refCallee.p:
And, in the internal procedure
fillProc:
Run
refCaller.pagain and you can see the proof. WhenrefCallee.pis first run, it gets a handle for its own ProDataSet:
![]()
Next, the calling procedure displays the handle of its copy of the ProDataSet:
![]()
Now it runs
fillProc:
![]()
You can see that
fillProc’sProDataSet has the same handle as the one in the calling procedurerefCaller.p. In fact, it is the same ProDataSet, the one with no Data-Sources.If you change the persistent procedure to do all its work in the internal procedure, then everything works, as shown:
Figure 2–1 shows what’s happening.
Figure 2–1: Passing ProDataSets
![]()
Procedure
Design tip: Don’t set ProDataSet handles at the main procedure level when they will be accessed in internal procedures. Set the handle variables where they are used to capture a reference to an externally defined ProDataSet. Design tip: It’s always good practice to perform operations such as attaching Data-Sources locally to where they are needed. It’s essential if the ProDataSet is being passed by reference. Note: These procedures use the standard include files for the temp-table and ProDataSet definitions. Adding therefCallee.phas a definition ofdsOrder, but the ProDataSet instance this represents is replaced by the one fromrefCaller.pwhen its ProDataSet is passedBY-REFERENCE. All internal references tohDsetare therefore invalid because they point to a ProDataSet instance that isn’t being used. This teaches two important lessons, as described in the following design tips.REFERENCE-ONLYkeyword to these definitions would improve the performance of these procedures by avoiding the instantiation of the called routine’s objects. It would also avoid the run-time errors by telling Progress at compile time that the called procedure's ProDataSet is not actually being used.Specifying BY-VALUE in the called procedure
The parameter list shared by the calling procedure and the called procedure represents a contract between the two procedures that defines how they exchange data. As the cases we explored above illustrate, passing a ProDataSet
BY-REFERENCEis a valuable optimization but one with side effects that change the nature of the contract between caller and callee. In some cases, the called procedure might want to force a ProDataSet parameter to be passed by value, regardless of any optimization used by the caller, to enforce the contract of its parameter list, and to avoid some of the side effects that can occur. For example, the called procedure might have some reason why it has to reference the ProDataSet handle in its main block and have that handle retain its validity inside internal procedures. Or, it might need to insist that anINPUTparameter should not result in the caller being able to see changes made to the ProDataSet in the called procedure. In any such case, the called procedure can include theBY-VALUEkeyword in its parameter definition to force the ProDataSet to be passed by value, regardless of the caller, as shown:
Importance of optimized code with BY-REFERENCE
Note: The information in this section applies to the use of BIND as well, which you will read about in the "Passing a ProDataSet parameter by binding" section.This diversion into a discussion of
BY-REFERENCEmight seem overly complex, but it has been introduced here for a reason. If you get into the habit of structuring your procedures and their ProDataSet parameters properly from the beginning, you will be well positioned to optimize most calls that are sometimes made locally and sometimes remotely by adding theBY-REFERENCEkeyword to your calls without any undesirable consequences. It is always best to design your procedures so that they work properly when run locally—even when in a deployed application they may be distributed on different machines. This makes the initial development and testing of your application more straightforward, and supports the case (even if it’s only for demo purposes) where everything is running in a single session. Since theBY-REFERENCEoptimization is such a valuable one, it is worth making the effort to prepare for it right from the start, even if you first code and test your application without it and then add the keyword to your calls to improve performance.You can pass a ProDataSet reference locally as a
HANDLE PARAMETERas you can with other objects such as temp-tables. This gives the called procedure access to the ProDataSet instance defined in the caller, but has two essential limitations. First, the called procedure must be in the same Progress session as the calling procedure. You can’t pass an object handle of any kind across an AppServer call and have it maintain its validity. Second, the called procedure can only reference the ProDataSet using dynamic attributes and methods if it receives it as aHANDLE. In other words, the called procedure can’t receive theHANDLEparameter into a staticDATASETdefinition and reference it using static table and field names.By contrast, when you pass or receive a ProDataSet as a
DATASET-HANDLE, this simply means that the procedure using theDATASET-HANDLEparameter form sees the ProDataSet as a dynamic object. The procedure on the other side of the call can see it as a static object. The table below shows the different combinations. Normally, passing a ProDataSet as aDATASET-HANDLEcauses its definition and data to be copied to the receiving procedure. Using theBY-REFERENCEoption on the parameter makes passing a ProDataSet as aDATASET-HANDLEcost no more within a single session than passing a reference to it as aHANDLE. For this reason, we recommend that you normally use theDATASET-HANDLEform. In this way, if the call is ever moved to a remote procedure, it will still work properly, whereas theHANDLEparameter will fail on a remote call.Passing a ProDataSet
BY-REFERENCEis particularly valuable when the called procedure uses theDATASETparameter form to receive the ProDataSet into a static definition. This will be the norm in most server-side procedures, and especially in most event handling procedures. The business logic in these procedures is much simpler to write if it uses static 4GL statements to refer to and manipulate the records in the ProDataSet’s temp-tables. All the elements of the ProDataSet definition, along with all the data in its temp-tables, are available to the called procedure. The default buffers that are part of the ProDataSet, which have the same names as their temp-tables, are in fact shared between caller and callee. Therefore, if the called procedure changes the record position in any of these buffers, this will be visible to the caller after the procedure returns. The called procedure can define buffers of its own to avoid this. Any changes to the ProDataSet data made by the called procedure are visible to the caller, as if the object wereSHARED.There are several restrictions in ProDataSet usage that are at least partially related to this parameter support:
As a final note, it is possible to specify a ProDataSet as a parameter for a dynamic
CALLobject, which lets you set up the entire definition of aRUNstatement dynamically. You can also specify theBY-REFERENCEqualifier in the arguments to the dynamicCALL. For more information, see the reference documentation or online help for theSET-PARAMETER( )method on the dynamicCALLobject.
|
Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095 |